﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LightCAD.Three
{
    public static class MathUtils
    {

        internal static double _seed = 1234567;
        public const int Infinity = int.MaxValue;
        public const double NaN = double.NaN;

        public const double DEG2RAD = JMath.PI / 180;
        public const double RAD2DEG = 180 / JMath.PI;

        // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136
        public static string generateUUID()
        {
            return Guid.NewGuid().ToString().ToLower();
        }

        public static double clamp(double value, double min, double max)
        {

            return JMath.max(min, JMath.min(max, value));

        }

        // compute euclidean modulo of m % n
        // https://en.wikipedia.org/wiki/Modulo_operation
        public static double euclideanModulo(double n, double m)
        {

            return ((n % m) + m) % m;

        }

        // Linear mapping from range <a1, a2> to range <b1, b2>
        public static double mapLinear(double x, double a1, double a2, double b1, double b2)
        {

            return b1 + (x - a1) * (b2 - b1) / (a2 - a1);

        }

        // https://www.gamedev.net/tutorials/programming/general-and-gameplay-programming/inverse-lerp-a-super-useful-yet-often-overlooked-function-r5230/
        public static double inverseLerp(double x, double y, double value)
        {

            if (x != y)
            {
                return (value - x) / (y - x);
            }
            else
            {
                return 0;
            }

        }

        // https://en.wikipedia.org/wiki/Linear_interpolation
        public static double lerp(double x, double y, double t)
        {

            return (1 - t) * x + t * y;

        }

        // http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/
        public static double damp(double x, double y, double lambda, double dt)
        {

            return lerp(x, y, 1 - JMath.exp(-lambda * dt));

        }

        // https://www.desmos.com/calculator/vcsjnyz7x4
        public static double pingpong(int x, int length = 1)
        {

            return length - JMath.abs(euclideanModulo(x, length * 2) - length);

        }

        // http://en.wikipedia.org/wiki/Smoothstep
        public static double smoothstep(double x, double min, double max)
        {

            if (x <= min) return 0;
            if (x >= max) return 1;

            x = (x - min) / (max - min);

            return x * x * (3 - 2 * x);

        }

        public static double smootherstep(double x, double min, double max)
        {

            if (x <= min) return 0;
            if (x >= max) return 1;

            x = (x - min) / (max - min);

            return x * x * x * (x * (x * 6 - 15) + 10);

        }

        // Random integer from <low, high> interval
        public static int randInt(int low, int high)
        {

            return low + (int)JMath.floor(JMath.random() * (high - low + 1));

        }

        // Random float from <low, high> interval
        public static double randFloat(double low, double high)
        {

            return low + JMath.random() * (high - low);

        }

        // Random float from <-range/2, range/2> interval
        public static double randFloatSpread(double range)
        {

            return range * (0.5 - JMath.random());

        }

        // Deterministic pseudo-random float in the interval [ 0, 1 ]
        public static double seededRandom(int s = int.MaxValue)
        {

            if (s != int.MaxValue) _seed = s;

            // Mulberry32 generator

            int t = (int)(_seed += 0x6D2B79F5);

            t = JMath.imul(t ^ t >> 15, t | 1);

            t ^= t + JMath.imul(t ^ t >> 7, t | 61);

            return ((t ^ t >> 14) >> 0) / 4294967296.0;

        }

        public static double degToRad(double degrees)
        {

            return degrees * DEG2RAD;

        }

        public static double radToDeg(double radians)
        {

            return radians * RAD2DEG;

        }

        public static bool isPowerOfTwo(int value)
        {

            return (value & (value - 1)) == 0 && value != 0;

        }

        public static double ceilPowerOfTwo(double value)
        {

            return JMath.pow(2, JMath.ceil(JMath.log(value) / JMath.LN2));

        }

        public static double floorPowerOfTwo(double value)
        {

            return JMath.pow(2, JMath.floor(JMath.log(value) / JMath.LN2));

        }

        public static void setQuaternionFromProperEuler(Quaternion q, double a, double b, double c, string order)
        {

            // Intrinsic Proper Euler Angles - see https://en.wikipedia.org/wiki/Euler_angles

            // rotations are applied to the axes in the order specified by 'order'
            // rotation by angle 'a' is applied first, then by angle 'b', then by angle 'c'
            // angles are in radians


            double c2 = JMath.cos(b / 2);
            double s2 = JMath.sin(b / 2);

            double c13 = JMath.cos((a + c) / 2);
            double s13 = JMath.sin((a + c) / 2);

            double c1_3 = JMath.cos((a - c) / 2);
            double s1_3 = JMath.sin((a - c) / 2);

            double c3_1 = JMath.cos((c - a) / 2);
            double s3_1 = JMath.sin((c - a) / 2);

            switch (order)
            {

                case "XYX":
                    q.set(c2 * s13, s2 * c1_3, s2 * s1_3, c2 * c13);
                    break;

                case "YZY":
                    q.set(s2 * s1_3, c2 * s13, s2 * c1_3, c2 * c13);
                    break;

                case "ZXZ":
                    q.set(s2 * c1_3, s2 * s1_3, c2 * s13, c2 * c13);
                    break;

                case "XZX":
                    q.set(c2 * s13, s2 * s3_1, s2 * c3_1, c2 * c13);
                    break;

                case "YXY":
                    q.set(s2 * c3_1, c2 * s13, s2 * s3_1, c2 * c13);
                    break;

                case "ZYZ":
                    q.set(s2 * s3_1, s2 * c3_1, c2 * s13, c2 * c13);
                    break;

                default:
                    console.warn("THREE.MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: " + order);
                    break;
            }

        }

        public static double denormalize(double value, Array array)
        {
            if (array is double[]) return value;

            if (array is UInt16[]) return value / 65535.0;

            if (array is byte[]) return value / 255.0;

            if (array is Int16[]) return JMath.max(value / 32767.0, -1.0);

            if (array is sbyte[]) return JMath.max(value / 127.0, -1.0);

            throw new Error("Invalid component type.");

        }


        public static double normalize(double value, Array array)
        {

            if (array is double[]) return value;

            if (array is UInt16[]) return JMath.round(value * 65535.0);

            if (array is byte[]) return JMath.round(value * 255.0);

            if (array is Int16[]) return JMath.round(value * 32767.0);

            if (array is sbyte[]) return JMath.round(value * 127.0);

            throw new Error("Invalid component type.");
        }

        public static bool isNaN(double val)
        {
            return double.IsNaN(val);
        }
        public static bool isFinite(double val)
        {
            return double.IsInfinity(val);
        }
    }

}